/*
 *  NFCSigning - Open source library for signing/validation of NDEF messages
 *  Copyright (C) 2009-2010 The NFCSigning Team
 *
 *  This library is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 2.1 of the License, or (at your option) any later version.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General Public
 *  License along with this library; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 *
 */
package org.nfcsigning;

import org.nfcsigning.algorithm.Signer;
import org.nfcsigning.record.CertificateField;
import org.nfcsigning.record.SignatureField;
import org.nfcsigning.record.SignatureRecord;
import java.util.Vector;

//#ifdef MyJSR257
//# import my.javax.microedition.contactless.ndef.NDEFMessage;
//# import my.javax.microedition.contactless.ndef.NDEFRecord;
//#else
import javax.microedition.contactless.ndef.NDEFMessage;
import javax.microedition.contactless.ndef.NDEFRecord;
//#endif

/**
 *
 * @author Markus Kilås
 */
public class MessageToSign {

    private NDEFMessage payload;
    private Signer[] signerForRecord;
        
    public MessageToSign(NDEFMessage payload) {
        this.payload = payload;
        this.signerForRecord = new Signer[payload.getNumberOfRecords()];
    }

    public void setSigner(int[] recordIndexes, Signer signer) {
        for(int i = 0; i < recordIndexes.length; i++) {
            signerForRecord[recordIndexes[i]] = signer;
        }
    }
    
    public void setSigner(int recordIndex, Signer signer) {
        signerForRecord[recordIndex] = signer;
    }
    
    public void setSigner(Signer signer) {
        for(int i = 0; i < signerForRecord.length; i++) {
            signerForRecord[i] = signer;
        }
    }

    public SignedMessage signMessage() throws org.nfcsigning.algorithm.SignatureException {
        return sign(payload, getSignatureRanges());
    }
    
    SignatureRange[] getSignatureRanges() {
     
        Vector v = new Vector();
        int begin = 0;
        int end = 0;
        Signer signer = null;
        for(int i = 0; i < signerForRecord.length; i++) {
            
            if(signer == null) {
                signer = signerForRecord[i];
                begin = i;
            } else if(signerForRecord[i] != signer) {
                
                SignatureRange range = new SignatureRange(begin, end, signer);
                v.addElement(range);
                begin = i;
            }
            end++;            
        }
        
        if(end-begin > 0) {
            SignatureRange range = new SignatureRange(begin, end, signer);
            v.addElement(range);
        }
            
        SignatureRange[] ranges = new SignatureRange[v.size()];
        for(int i = 0; i < v.size(); i++) {
            ranges[i] = (SignatureRange) v.elementAt(i);
        }
        
        return ranges;
    }
    
    static SignedMessage sign(NDEFMessage origMsg, SignatureRange[] sigRanges) throws org.nfcsigning.algorithm.SignatureException {
        
        Signature[] records = new Signature[origMsg.getNumberOfRecords()];
        

        for(int j = 0; j < sigRanges.length; j++) {

            int beginIndex = sigRanges[j].getBeginIndex();
            int endIndex = sigRanges[j].getEndIndex();
            Signer signer = sigRanges[j].getSigner();

            // Sign the bytes
            byte[] coveredBytes = Canonicalizer.toCanonicalizedForm(origMsg.getRecords(), beginIndex, endIndex);
            byte[] signatureBytes;
            signatureBytes = signer.sign(coveredBytes);

            // New Signature and Signature Record
            SignatureRecord sigRec = new SignatureRecord(new SignatureField(signer.getAlgorithm(), signatureBytes), new CertificateField(signer.getCertificateType(), signer.getCertificateChain(), signer.getCertificateURI()));
            Signature sig = new Signature(sigRec, signer.getSignatureRecordType());

            for(int k = beginIndex; k < endIndex; k++) {
                records[k] = sig;
            }
        }

        // Merge msg.getRecords with the new SignatureRecords
        final Vector newRecords = putTogetherRecords(origMsg.getRecords(), records);
        
        return new SignedMessage(records) {

            public NDEFMessage toNDEFMessage() {
                NDEFRecord[] records = new NDEFRecord[newRecords.size()];

                for(int i = 0; i < records.length; i++) {
                    Object o = newRecords.elementAt(i);

                    if(o instanceof NDEFRecord) {
                        records[i] = (NDEFRecord) o;
                    } else if(o instanceof Mark) {
                        Signature sig = ((Mark)o).getSignature();
                        records[i] = SignatureRecord.createMark().toNDEFRecord(sig.getSignatureRecordType(), null);
                    } else if(o instanceof Signature) {
                        Signature sig = ((Signature)o);
                        records[i] = sig.getSignatureRecord().toNDEFRecord(sig.getSignatureRecordType(), null);
                    } else {
                        throw new RuntimeException("Unexpected object in newRecords");
                    }
                }
                return new NDEFMessage(records);
            }
        };
    }
    
    static class Mark {
        private Signature signature;

        public Mark(Signature signature) {
            this.signature = signature;
        }

        public Signature getSignature() {
            return signature;
        }
    }

    static Vector putTogetherRecords(NDEFRecord[] origRecords, Signature[] records) {
        Vector newRecords = new Vector();
        
        Signature sig = records[0];
        newRecords.addElement(origRecords[0]);
        
        for(int i = 1; i < records.length; i++) {
            
            if (records[i] == null && sig != null) {    // Close this signature range
                newRecords.addElement(sig);
                sig = null;
            } else if(records[i] != null && sig == null) {  // Start new range
                sig = records[i];
                newRecords.addElement(new Mark(sig));
            }
            newRecords.addElement(origRecords[i]);  // Add the record
        }
        
        if(sig != null) {
            newRecords.addElement(sig);
        }
        
        return newRecords;
    }
}
